/*
 * Document.hpp
 *
 *  Created on: 30.09.2012
 *      Author: dom
 */

#ifndef DOCUMENT_HPP_
#define DOCUMENT_HPP_

#include <hash_set>
#include <cmath>
//#include <iterator>
#include <string>
#include <map>
#include <stdexcept>

#include "document_class.hpp"

namespace mpei {

typedef enum {
	TF, TF_IDF, TFC, LTC, ATC,
} TypeFreq;

//typedef enum {
//	STEM_NO, STEM_PORTER,
//} Stemming;

template<typename _T, template<typename _Tp,
		typename _Alloc = std::allocator<_Tp> > class _Container = std::vector,
		typename _Str = std::string, typename _D = double>
class Document {
	typedef typename _T::key_type _key_type;
	typedef typename _T::value_type _value_type;
	typedef DocumentClass<Document<_T> > _class;
public:
	typedef Document<_T, _Container, _Str>* ptr;
	typedef _Str name_type;
	typedef _T value_type;
	typedef std::shared_ptr<_T> value_shared_ptr;
	typedef std::shared_ptr<Document<_T, _Container, _Str> > shared_ptr;
	typedef _Container<value_shared_ptr> containter;
	typedef typename _Container<value_shared_ptr>::iterator iterator;

	Document() :
			document_class(nullptr) {
	}

	Document(const _Str & name) :
			document_class(nullptr), document_name(name) {
	}

	virtual ~Document() {
		//todo  delete pointers in container
	}

//	void insert(_Tptr & token) {
//		auto token_ = value_shared_ptr(token);
//		if (std::find(tokens.begin(),tokens.end(),token_) != tokens.end()) {
//			++(token_);
//		} else {
//		token_->setDocument(this);
//		tokens.push_back(token_);
//		++(token);
//		}
//	}

	void insert(value_shared_ptr & token) {
		auto it = std::find(tokens.begin(), tokens.end(), token);
		if (it != tokens.end()) {
			++(*it);
		} else {
			token->setDocument(this);
			tokens.push_back(token);
			++(token);
		}
	}

	void toCentroid(size_t size) {
		for (auto token : tokens) {
			auto freq_ = token->getFrequency() / size;
			token->setFrequency(freq_);
			calculate_tf(token);
			calculate_tfidf(token);
		}
	}

	void calculate() {
		for (auto token : tokens) {
			calculate_tf(token);
			calculate_tfidf(token);
		}
	}
	void calculate_tf(value_shared_ptr & token_) {
		// tf
		size_t size_ = tokens.size();
		if (document_class == nullptr) {
			throw std::runtime_error(document_name + " is not class token");
		}
//		std::cout <<"Token: " << token_->data() << " Frequency " <<  token_->getFrequency()<<std::endl;
		_value_type tf_weight_ = token_->getFrequency() / size_;
		token_->setTf(tf_weight_);
//		std::cout <<"Token: " <<token_->data() << " tf " <<  token_->getTf()<<std::endl;
	}

	void calculate_tfidf(value_shared_ptr & token_) {
		//tfidf
		_value_type num_other_doc_ = 0;
		size_t class_size_ = document_class->get().size();
		for (auto document_ : document_class->get()) {
			if (document_.get() == this) {
				continue;
			}
			if (document_->find(token_) != document_->get().end()) {
				++num_other_doc_;
			}
		}
		if (num_other_doc_) {
			_value_type tf_idf_weight_ = class_size_ / num_other_doc_;
			tf_idf_weight_ = token_->getFrequency() * std::log(tf_idf_weight_);
			token_->setTfIdf(tf_idf_weight_);
//			std::cout <<"Token: " <<token_->data() << " tfidf " <<  token_->getTfIdf()<<std::endl;
		} else {
			_value_type tf_idf_weight_ = 0;
			token_->setTfIdf(tf_idf_weight_);
		}
	}

	void calculate_tfc(value_shared_ptr & token_) {

	}
//	iterator find(_Tptr & token) {
//		auto token_ = value_shared_ptr(token);
//		return std::find(tokens.begin(),tokens.end(),token);
////		return tokens.find(token_);
////		if (it_ != tokens.end()) {
////			return it_;
////		} else {
////			return nullptr;
////		}
//	}

	iterator find(value_shared_ptr & token) {
		return std::find(tokens.begin(), tokens.end(), token);
//		return tokens.find(token);
//		if (it_ != tokens.end()) {
//			return it_;
//		} else {
//			return nullptr;
//		}
	}

	_Container<value_shared_ptr> & get() {
		return this->tokens;
	}

//	void get(map & map, TypeFreq type_freq) {
//		size_t size_ = tokens.size();
//		size_t class_size_ = document_class->get().size();
//		switch (type_freq) {
//		case TF:
//			for (auto token_ : tokens) {
//				_value_type tf_weight_ = token_->getFrequency() / size_;
//				token_->setTf(tf_weight_);
//				map.insert(pair(token_->data(), tf_weight_));
//			}
//			break;
//		case TF_IDF:
//			for (auto token_ : tokens) {
//				_value_type num_other_doc_ = 0;
//				for (auto document_ : document_class->get()) {
//					if (document_.get() == this) {
//						continue;
//					}
//					if (document_->find(token_)) {
//						num_other_doc_++;
//					}
//				}
//				if (num_other_doc_) {
//					_value_type tf_idf_weight_ = class_size_ / num_other_doc_;
//					tf_idf_weight_ = log(tf_idf_weight_);
//					token_->setTfIdf(tf_idf_weight_);
//					map.insert(pair(token_->data(), tf_idf_weight_));
//				}
//			}
//			break;
//		default:
//			break;
//		}
//	}

	_class * getDocumentClass() {
		return document_class;
	}

	void setDocumentClass(_class * doc_class) {
		document_class = doc_class;
	}

	void setDocumentName(const _Str & str) {
		this->document_name = str;
	}

	_Str & getDocumentName() {
		return this->document_name;
	}

	void setAnnotation(const _Str & txt) {
		annotation = txt;
	}

	_Str & getAnnotation() {
		return annotation;
	}

	void setDistanceToMean(const ptr & mean, _D & distance) {
		distance_to_means.insert(std::pair<ptr, _D>(mean, distance));
	}
//	_D getDistanceToMean(shared_ptr & mean) {
//		auto tmp_= distance_to_means[mean];
//		return tmp_;
//	}

	_D getDistanceToMean(const ptr & mean) {
//		for (auto tmp_ : distance_to_means) {
//			std::cout << tmp_.first << std::endl;
//		}
//		std::cout << "mean" << mean << std::endl;

		auto tmp_ = distance_to_means[mean];
		return tmp_;
	}

//	friend inline bool operator<(const Document& __rhs, const Document& __lhs) {
//		return __lhs.document_name < __rhs.document_name;
//	}
//
//	friend inline bool operator>(const Document& __rhs, const Document& __lhs) {
//		return __lhs.document_name > __rhs.document_name;
//	}

// TODO make centroid class : public Document
	void setDistance(shared_ptr & document, _D distance) {
		distance_means.push_back(std::pair<_D, shared_ptr>(distance, document));
		std::sort(distance_means.begin(), distance_means.end());
	}

	void findRadius(_D distance) {
		auto it = std::find_if(distance_means.begin(), distance_means.end(),
				[&](std::pair<_D,shared_ptr> pair_)->bool {
					return pair_.first > distance;
				});
//		std::cout << "findRadius" << std::endl;
//		std::cout << "> distance: " <<it->second->getDocumentName()  << " : "<< it->first<< std::endl;
		--it;
//		std::cout << "dist: " << distance << std::endl;
//		std::cout << "radius: "<<it->second->getDocumentName()  << " : "<< it->first<< std::endl;
		if (it != distance_means.end()) {
			doc_radius = it->second;
		}
	}

	_D getRadius() {
		return doc_radius->getDistanceToMean(this);
	}

	void setDocRadius(shared_ptr & doc_radius) {
		this->doc_radius = doc_radius;
	}

	std::set<shared_ptr> getDocDelta(uint32_t index) {
		auto it = std::find_if(distance_means.begin(), distance_means.end(),
				[&](std::pair<_D,shared_ptr> pair_) -> bool {
					return pair_.second == doc_radius;
				});
//		std::cout << "getDocDelta" << std::endl;
//		std::cout << "from: "<< it->second->getDocumentName() << " distance: " << it->first << std::endl;
//		std::cout << "to: "<< (it + index)->second->getDocumentName()<< " distance: " << (it + index)->first << std::endl;

		std::set<shared_ptr> container_;
		for(; (it != it + index) && (it != distance_means.end()); ++it) {
//			std::cout << "it: "<< it->second->getDocumentName() << " distance: " << it->first << std::endl;
			container_.insert(it->second);
		}
		return container_;
//		return std::vector<shared_ptr>(it->second, (it + index)->second);
//		if(it == distance_means.end()) {
//			return std::numeric_limits<_D>::max();
//		}
//		uint32_t i = 0;
//		for (; it != distance_means.end() && i < index; ++i) {
//			++it;
//		}
//		if (it == distance_means.end()) {
//			return 0;
//		}
//		auto doc = (*it).second;
//		auto tmp = doc->getDistanceToMean(this);
//		return tmp;
	}

	_D getRadiusDelta(uint32_t index) {
		auto it = std::find_if(distance_means.begin(), distance_means.end(),
				[&](std::pair<_D,shared_ptr> pair_) -> bool {
					return pair_.second == doc_radius;
//					return pair_.first == doc_radius->getDistanceToMean(this);
				});
		if (it == distance_means.end()) {
			--it;
			auto doc = (*it).second;
			auto tmp = doc->getDistanceToMean(this);
			return tmp;
		}
		uint32_t i = 0;
		for (; it != distance_means.end() && i < index; ++i) {
			++it;
		}
		if (it == distance_means.end()) {
			return 0;
		}
		auto doc = (*it).second;
		auto tmp = doc->getDistanceToMean(this);
		return tmp;
	}

protected:
	_class * document_class;
	_Container<value_shared_ptr> tokens;
	_Str document_name;
	_Str annotation;
	std::vector<std::pair<_D, shared_ptr>> distance_means; //centroid
	std::map<ptr, _D> distance_to_means;
	shared_ptr doc_radius;
};

//template<typename _T, template<class _Value, class _HashFcn, class _EqualKey,
//		class _Alloc> class _Container, typename _Str>
//Document<_T, _Container, _Str>::Document(_Str & name) : document_name(name) {
//
//}

}// namespace mpei

#endif /* DOCUMENT_HPP_ */
